//
//  SFCClient.m
//  SVNForCocoa
//
//  Created by Jeremy Pereira on 07/11/2013.
//  Copyright (c) 2013 Jeremy Pereira. All rights reserved.
//
/*!
 *    @file
 *    @brief Implementation of SFCClient.
 */

#import "SFCClient.h"
#import "SFCLib+Internals.h"
#import "SFCError.h"
#import "SFCAuthenticationProvider.h"
#import "SFCCredentials.h"
#import "SFCLock.h"
#import "SFCCommitItem.h"
#import <svn_version.h>


/*!
 *    @brief Internal methods and properties for the SFCClient class.
 */
@interface SFCClient()

@property (nonatomic, assign) svn_client_ctx_t* svnClientContext;
@property (nonatomic, readonly, strong) SFCAPRPool* pool;
/*!
 *    @brief A child pool of the pool for the auth stuff so that we can reclaim
 *           the memory when we add a new authentication provider.
 */
@property (nonatomic, strong) SFCAPRPool* authPool;

+(apr_array_header_t*) aprArrayOfRevisionRangesFrom: (NSArray*) arrayOfRevisionRanges
                                           withPool: (SFCAPRPool*) pool;

+(NSDictionary*) dictionaryFromAprAuthParameters: (apr_hash_t*) aprAuthParameters
                                         aprPool: (apr_pool_t*) aprPool
                                           error: (NSError* __autoreleasing*) error;

@end

static svn_error_t* svnCommitCallback(const svn_commit_info_t *commit_info,
                                      void *baton,
                                      apr_pool_t *pool);
static svn_error_t* svnLogEntryCallback(void *baton,
                                        svn_log_entry_t *log_entry,
                                        apr_pool_t *pool);
static svn_error_t* svnStatusCallback(void *baton,
                                      const char* path,
                                      const svn_client_status_t* status,
                                      apr_pool_t *pool);
static void svnNotifyCallback(void* baton,
                              const svn_wc_notify_t* notification,
                              apr_pool_t* pool);
static svn_error_t* clientInfoReceiver(void* baton,
                                       const char* path,
                                       const svn_client_info2_t* svnInfo,
                                       apr_pool_t* pool);
static svn_error_t *handleFirstCredentials(void **credentials,
                                           void **iter_baton,
                                           void *provider_baton,
                                           apr_hash_t *parameters,
                                           const char *realmstring,
                                           apr_pool_t *pool);
static svn_error_t *handleNextCredentials(void **credentials,
                                          void *iter_baton,
                                          void *provider_baton,
                                          apr_hash_t *parameters,
                                          const char *realmstring,
                                          apr_pool_t *pool);
static svn_error_t *handleSaveCredentials(svn_boolean_t *saved,
                                          void *credentials,
                                          void *provider_baton,
                                          apr_hash_t *parameters,
                                          const char *realmstring,
                                          apr_pool_t *pool);
static svn_error_t* handleLogMessage(const char** message,
                                     const char** tempFile,
                                     const apr_array_header_t* commitItems,
                                     void* baton,
                                     apr_pool_t* pool);

static apr_status_t cleanUpIterator(void* iterator);

static svn_error_t* clientBlameReceiver(void *baton,
                                        svn_revnum_t start_revnum,
                                        svn_revnum_t end_revnum,
                                        apr_int64_t line_no,
                                        svn_revnum_t revision,
                                        apr_hash_t *rev_props,
                                        svn_revnum_t merged_revision,
                                        apr_hash_t *merged_rev_props,
                                        const char *merged_path,
                                        const char *line,
                                        svn_boolean_t local_change,
                                        apr_pool_t *pool);

static svn_error_t* listFuncReceiver(void* baton,
                                     const char* path,
                                     const svn_dirent_t* dirEnt,
                                     const svn_lock_t* lock,
                                     const char* absPath,
                                     const char* externalParentUrl,
                                     const char* externalTarget,
                                     apr_pool_t* pool);

static svn_error_t* propListReceiver(void *baton,
                                     const char *path,
                                     apr_hash_t *prop_hash,
                                     apr_array_header_t *inherited_props,
                                     apr_pool_t *pool);
static svn_error_t* importFilter(void *baton,
                                 svn_boolean_t *filtered,
                                 const char *local_abspath,
                                 const svn_io_dirent2_t *dirent,
                                 apr_pool_t *scratch_pool);
static svn_error_t* patchCallback(void *baton,
                                 svn_boolean_t *filtered,
                                 const char *canon_path_from_patchfile,
                                 const char *patch_abspath,
                                 const char *reject_abspath,
                                 apr_pool_t *scratch_pool);
static svn_error_t* conflictResolver(svn_wc_conflict_result_t **result,
                                     const svn_wc_conflict_description2_t *description,
                                     void *baton,
                                     apr_pool_t *resultPool,
                                     apr_pool_t *scratchPool);


@interface SFCVersion()

@end
/*!
 *    @brief This is our equivalent of an svn_auth_provider_object_t.
 *
 *    We maintain an array of these internally as the list of authentication
 *    providers.
 */
@interface SFCAuthProviderEntry : SFCObject
/*!
 *    @brief A pointer to the real avn_auth_provider_object_t
 */
@property (nonatomic, readonly, assign) svn_auth_provider_object_t* svnAuthProviderObject;
/*!
 *    @brief Initialise an auth provider entry.
 *
 *    @param newAuthProvider The authentication provider.
 *
 *    @return A new SFCAuthProiderEntry.
 */
-(id) initWithAuthenticationProvider: (id<SFCAuthenticationProvider>) newAuthProvider;

@end

@implementation SFCAuthProviderEntry
{
    svn_auth_provider_object_t providerObject;
    svn_auth_provider_t vtable;
    id<SFCAuthenticationProvider> authenticationProvider;
}

-(id) initWithAuthenticationProvider: (id<SFCAuthenticationProvider>) newAuthProvider
{
	self = [super init];
    if (self != nil)
    {
		authenticationProvider = newAuthProvider;

        vtable.cred_kind = [[newAuthProvider kind] UTF8String];
        vtable.first_credentials = handleFirstCredentials;
        vtable.next_credentials = handleNextCredentials;
        vtable.save_credentials = handleSaveCredentials;

        providerObject.vtable = &vtable;
        providerObject.provider_baton = (__bridge void *)(newAuthProvider);
    }
    return self;
}

-(svn_auth_provider_object_t*) svnAuthProviderObject
{
    return &providerObject;
}

@end

@implementation SFCClient
{
    NSMutableArray* authenticationProviders;
}

@synthesize svnClientContext = _svnClientContext;
@synthesize pool = _pool;
@synthesize notifyBlock = _notifyBlock;
@synthesize authPool = _authPool;
@synthesize commitLogBlock = _commitLogBlock;
@synthesize conflictResolver = _conflictResolver;


-(id) initWithError: (NSError *__autoreleasing*) error
{
    self =[super init];

    if (self != nil)
    {
		_pool = [SFCAPRPool makePoolWithParent: nil error: error];
        if (_pool == nil)
        {
            self = nil;
        }
        else
        {
            svn_error_t* svnError = svn_client_create_context2(&_svnClientContext,
                                                               NULL,
                                                               [[self pool] aprPool]);
			if (svnError != NULL)
            {
				if (error != NULL)
                {
					*error = [SFCError svnError: svnError];
                }
                self = nil;
            }
            else
            {
                [self setNotifyBlock: ^(SFCWCNotification* notification) {}];
				_svnClientContext->notify_func2 = svnNotifyCallback;
                authenticationProviders = [[NSMutableArray alloc] init];
            }
        }
    }
    else if (error != nil)
    {
		*error = [SFCError selfInitFail: [self className]];
    }

    return self;
}

-(id) init
{
    return [self initWithError: nil];
}

-(void) dealloc
{
    [self setNotifyBlock: NULL];
}

+(SFCVersion*) version
{
    static SFCVersion* theVersion = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,
    ^{
        theVersion = [[SFCVersion alloc] init];
    });
    return theVersion;
}

-(void) setNotifyBlock:(SFCClientNotifyBlock)notifyBlock
{
    void* oldBlock = [self svnClientContext]->notify_baton2;
    [self svnClientContext]->notify_baton2 = (void*)CFBridgingRetain(notifyBlock);
    if (oldBlock != NULL)
    {
        CFRelease(oldBlock);
    }
}

-(void) setCommitLogBlock: (SFCGetCommitLog)commitLogBlock
{
    void* oldBlock = [self svnClientContext]->log_msg_baton3;
    [self svnClientContext]->log_msg_baton3 = (void*)CFBridgingRetain(commitLogBlock);
    if (oldBlock != NULL)
    {
        CFRelease(oldBlock);
    }
    if (commitLogBlock != nil)
    {
        [self svnClientContext]->log_msg_func3 = handleLogMessage;
    }
    else
    {
        [self svnClientContext]->log_msg_func3 = NULL;
    }
}

-(void) setConflictResolver: (SFCConfictResolverBlock) newBlock
{
    void* oldBlock = [self svnClientContext]->conflict_baton2;
    [self svnClientContext]->conflict_baton2 = (void*)CFBridgingRetain(newBlock);
    if (oldBlock != NULL)
    {
        CFRelease(oldBlock);
    }
    if (newBlock != nil)
    {
        [self svnClientContext]->conflict_func2 = conflictResolver;
    }
    else
    {
        [self svnClientContext]->log_msg_func3 = NULL;
    }
}

-(SFCClientNotifyBlock)notifyBlock
{
    return (__bridge SFCClientNotifyBlock)([self svnClientContext]->notify_baton2);
}

-(bool) checkoutURL: (NSURL*) url
               path: (NSString*) path
        pegRevision: (SFCRevision*) pegRevision
           revision: (SFCRevision*) revision
              depth: (SFCDepth) depth
    ignoreExternals: (bool) ignoreExternals
allowUnversionedObstructions: (bool) allowUnversionedObstructions
     resultRevision: (SFCRevNum*) resultRevision
              error: (NSError* __autoreleasing*) error
{
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }
    if (revision == nil)
    {
        revision = [SFCRevision head];
    }
    /*
     *  Subversion doesn't like trailing / characters on its URIs
     */
    NSString* urlString = [url absoluteString];
    if ([urlString hasSuffix: @"/"])
    {
        urlString = [urlString substringToIndex: [urlString length] - 1];
    }

    svn_error_t* result = svn_client_checkout3(resultRevision,
                                               [urlString UTF8String],
                                               [path UTF8String],
                         			   		   [pegRevision svnOptRevision],
                         			   		   [revision svnOptRevision],
                         			   		   (svn_depth_t) depth,
                         			   		   ignoreExternals,
                                       		   allowUnversionedObstructions,
                         			   		   [self svnClientContext],
                         			   		   [[self pool] aprPool]);
    if (result != NULL && error != NULL)
    {
		*error = [SFCError svnError: result];
    }
    return result == NULL;
}

#define IS_OPTION_SET(OPT,FLAGS)	(((FLAGS) & (OPT)) != 0)

-(bool) commit: (NSArray*) targets
         depth: (SFCDepth) depth
       options: (uint32_t) options
    changLists: (NSArray*) changeLists
  revPropTable: (NSDictionary*) revPropTable
committedBlock: (SFCCommittedBlock) committedBlock
         error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
    apr_array_header_t* aprTargets = [targets aprArrayOfUTF8StringsWithPool: scratchPool];
    apr_array_header_t* aprChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratchPool];
    apr_hash_t* aprRevisionProperties = [revPropTable aprHashOfUTF8StringsWithPool: scratchPool];
    svn_error_t* svnError = svn_client_commit6(aprTargets,
                                               (svn_depth_t) depth,
                                               IS_OPTION_SET(SFCCommitOptionKeepLocks, options),
                                               IS_OPTION_SET(SFCCommitOptionKeepChangeLists, options),
                                               IS_OPTION_SET(SFCCommitOptionCommitAsOperations, options),
                                               IS_OPTION_SET(SFCCommitOptionIncludeFileExternals, options),
                                               IS_OPTION_SET(SFCCommitOptionIncludeDirectoryExternals, options),
                                               aprChangeLists,
                                               aprRevisionProperties,
                                               svnCommitCallback,
                                               (__bridge void *)(committedBlock),
                                               [self svnClientContext],
                                               [scratchPool aprPool]);
    if (svnError != NULL && error != NULL)
    {
        /*
         *  If the error is bigger than any SVN error, it was created by the 
         *  commit callback.  Thus we create a custom NSError rather than one 
         *  with the svn error.
         */
        if (svnError->apr_err > SVN_ERR_LAST)
        {
            *error = [SFCError errorWithDomain: SVN_ERROR_DOMAIN
                                          code: svnError->apr_err - SVN_ERR_LAST - 1
                          localizedDescription: [NSString stringWithUTF8String: svnError->message]];
        }
        else
        {
            *error = [SFCError svnError: svnError];
        }
    }
    [scratchPool destroy];
    return svnError == NULL;
}

-(bool)    log: (NSArray*) target
   pegRevision: (SFCRevision*) pegRevision
revisionRanges: (NSArray*) revisionRanges
         limit: (int) limit
       options: (uint32_t) options
  revPropTable: (NSArray*) revPropTable
      logBlock: (SFCLogBlock) logBlock
         error: (NSError* __autoreleasing*) error
{
    apr_array_header_t* aprTargets = [target aprArrayOfUTF8StringsWithPool: [self pool]];
    apr_array_header_t* aprRevProps = NULL;
    if (revPropTable != nil)
    {
        aprRevProps = [revPropTable aprArrayOfUTF8StringsWithPool: [self pool]];
    }

    apr_array_header_t* aprRevRanges = [[self class] aprArrayOfRevisionRangesFrom: revisionRanges
                                                                     withPool: [self pool]];
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }

    svn_error_t* svnError = svn_client_log5(aprTargets,
                                            [pegRevision svnOptRevision],
                                            aprRevRanges,
                                            limit,
                                            IS_OPTION_SET(SFC_LOG_OPTION_DISCOVER_CHANGED_PATHS, options),
                                            IS_OPTION_SET(SFC_LOG_OPTION_STRICT_NODE_HISTORY, options),
                                            IS_OPTION_SET(SFC_LOG_OPTION_INCLUDE_MERGED_REVISIONS, options),
                                            aprRevProps,
                                            svnLogEntryCallback,
                                            (__bridge void *)(logBlock),
                                            [self svnClientContext],
                                            [[self pool] aprPool]);

    bool ret = svnError == nil;
    if (!ret)
    {
        if (error != nil)
        {
            /*
             *  If the error is bigger than any SVN error, it was created by the
             *  commit callback.  Thus we create a custom NSError rather than one
             *  with the svn error.
             */
            if (svnError->apr_err > SVN_ERR_LAST)
            {
                *error = [SFCError errorWithDomain: SVN_ERROR_DOMAIN
                                              code: svnError->apr_err - SVN_ERR_LAST - 1
                              localizedDescription: [NSString stringWithUTF8String: svnError->message]];
            }
            else
            {
                *error = [SFCError svnError: svnError];
            }
        }
        svn_error_clear(svnError);
    }
    return ret;
}

-(bool) status: (NSString*) path
      revision: (SFCRevision*) revision
         depth: (SFCDepth) depth
       options: (uint32_t) options
   changeLists: (NSArray*) changeLists
   statusBlock: (SFCStatusBlock) statusBlock
resultRevision: (SFCRevNum*) resultRevision
         error: (NSError* __autoreleasing*) error
{
    apr_array_header_t* aprChangeLists = NULL;
    if (changeLists != nil)
    {
        aprChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: [self pool]];
    }
    svn_error_t* statusError = svn_client_status5(resultRevision,
                                                  [self svnClientContext],
                                                  [path UTF8String],
                                                  [revision svnOptRevision],
                                                  (svn_depth_t)depth,
                                                  IS_OPTION_SET(SFC_STATUS_OPTION_GET_ALL, options),
                                                  IS_OPTION_SET(SFC_STATUS_OPTION_UPDATE, options),
                                                  IS_OPTION_SET(SFC_STATUS_OPTION_NO_IGNORE, options),
                                                  IS_OPTION_SET(SFC_STATUS_OPTION_IGNORE_EXTERNALS, options),
                                                  IS_OPTION_SET(SFC_STATUS_OPTION_DEPTH_AS_STICKY, options),
                                                  aprChangeLists,
                                                  svnStatusCallback,
                                                  (__bridge void *)(statusBlock),
                                                  [[self pool] aprPool]);
    bool ret = statusError == NULL;
    if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: statusError];
        }
        svn_error_clear(statusError);
    }
    return ret;
}

-(bool) update: (NSArray*) paths
      revision: (SFCRevision*) revision
         depth: (SFCDepth) depth
       options: (uint32_t) options
resultRevisions: (NSArray* __autoreleasing*) resultRevisions
         error: (NSError* __autoreleasing*) error
{
    apr_array_header_t* aprPaths = [paths aprArrayOfUTF8StringsWithPool: [self pool]];
    if (revision == nil)
    {
        revision = [SFCRevision head];
    }
	apr_array_header_t* resultRevs = NULL;
	svn_error_t* svnError = svn_client_update4(&resultRevs,
                                               aprPaths,
                                               [revision svnOptRevision],
                                               (svn_depth_t)depth,
                                               IS_OPTION_SET(SFC_UPDATE_OPTION_DEPTH_IS_STICKY, options),
                                               IS_OPTION_SET(SFC_UPDATE_OPTION_IGNORE_EXTERNALS, options),
                                               IS_OPTION_SET(SFC_UPDATE_OPTION_ALLOW_UNVERSIONED_OBSTRUCTIONS, options),
                                               IS_OPTION_SET(SFC_UPDATE_OPTION_ADDS_AS_MODIFICATION, options),
                                               IS_OPTION_SET(SFC_UPDATE_OPTION_MAKE_PARENTS, options),
                                               [self svnClientContext],
                                               [[self pool] aprPool]);
    bool ret = svnError == NULL;
    if (ret)
    {
		if (resultRevisions != nil)
        {
            *resultRevisions = [NSArray arrayWithAprArray: resultRevs
                                          objectConverter:^id(const void *svnObject, size_t size)
                                            {
                                                SFCRevNum revision = *(svn_revnum_t*) svnObject;
                                                return [NSNumber numberWithLong: revision];
                                            }];
        }
    }
    else
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;
}

-(bool) add: (NSString*) path
      depth: (SFCDepth) depth
    options: (uint32_t) options
      error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
	svn_error_t* svnError = svn_client_add5([path UTF8String],
                                            (svn_depth_t)depth,
                                            IS_OPTION_SET(SFC_ADD_FORCE, options),
                                            IS_OPTION_SET(SFC_ADD_NO_IGNORE, options),
                                            IS_OPTION_SET(SFC_ADD_NO_AUTOPROPS, options),
                                            IS_OPTION_SET(SFC_ADD_ADD_PARENTS, options),
                                            [self svnClientContext],
                                            [scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;
}

-(bool) revert: (NSArray*) paths
         depth: (SFCDepth) depth
   changeLists: (NSArray*) changeLists
         error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
    apr_array_header_t* aprPaths = [paths aprArrayOfUTF8StringsWithPool: scratchPool];
    apr_array_header_t* aprChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratchPool];
	svn_error_t* svnError = svn_client_revert2(aprPaths,
                                               (svn_depth_t)depth,
                                               aprChangeLists,
                                               [self svnClientContext],
                                               [scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;

}

-(bool) infoPath: (NSString*) path
     pegRevision: (SFCRevision*) pegRevision
        revision: (SFCRevision*) revision
           depth: (SFCDepth) depth
   fetchExcluded: (bool) fetchExcluded
 fetchActualOnly: (bool) fetchActualOnly
     changeLists: (NSArray*) changeLists
       infoBlock: (bool (^)(NSString* path, SFCClientInfo* info, NSError* __autoreleasing* error)) infoBlock
           error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
    apr_array_header_t* aprChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratchPool];
    const svn_opt_revision_t* svnPegRevision = NULL;
    const svn_opt_revision_t* svnRevision = NULL;
    if (pegRevision != nil)
    {
        svnPegRevision = [pegRevision svnOptRevision];
    }
    if (revision != nil)
    {
		svnRevision = [revision svnOptRevision];
    }
    /*
     *  Try to determine if this is a URL with a simple test.
     */
    NSRange endOfSchemeRange = [path rangeOfString: @"://"];
    if (endOfSchemeRange.location == NSNotFound)
    {
    /*
     *  It's a path.  Make it absolute.
     */
        if (![path isAbsolutePath])
        {
    		path = [[[NSFileManager defaultManager] currentDirectoryPath]
                    stringByAppendingPathComponent: path];
            path = [path stringByStandardizingPath];
        }
    }

    svn_error_t* svnError = svn_client_info3([path UTF8String],
                                             svnPegRevision,
                                             svnRevision,
                                             (svn_depth_t)depth,
                                             fetchExcluded,
                                             fetchActualOnly,
                                             aprChangeLists,
                                             clientInfoReceiver,
                                             (__bridge void *)(infoBlock),
                                             [self svnClientContext],
                                             [scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;
}

-(bool)      mkDir: (NSArray*) paths
       makeParents: (bool) makeParents
revisionProperties: (NSDictionary*) revisionProperties
    committedBlock: (SFCCommittedBlock) committedBlock
             error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
	apr_array_header_t* svnPaths
    	= [paths aprArrayWithSize: [paths count]
                  objectConverter: ^(void *destination, id object, SFCAPRPool *pool)
                  {
                	NSString* pathAsDefiniteString = object;
                	if ([object isKindOfClass: [NSURL class]])
                	{
                       pathAsDefiniteString = [object absoluteString];
                	}
                	*(const char**)destination = [pathAsDefiniteString svnStringWithPool: pool]->data;
                  }
                        	pool: scratchPool];
    apr_hash_t* svnRevProps
    	= [revisionProperties aprHashWithKeyConverter:^(id keyIn, const void **keyOut, apr_ssize_t *keyLength, SFCAPRPool *pool)
                                        {
                                            *keyOut = [keyIn svnStringWithPool: pool]->data;
                                            *keyLength = APR_HASH_KEY_STRING;
                                        }
                                      objectConverter:^(id objectIn, const void **objectOut, SFCAPRPool *pool)
                                        {
                                            *objectOut = [objectIn svnStringWithPool: pool];
                                        }
                                                 pool: scratchPool];
    svn_error_t* svnError = svn_client_mkdir4(svnPaths,
                                              makeParents,
                                              svnRevProps,
                                              svnCommitCallback,
                                              (__bridge void *)(committedBlock),
                                              [self svnClientContext],
                                              [scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;
}

-(bool) lock: (NSArray*) paths
     comment: (NSString*) comment
   stealLock: (bool) stealLock
       error: (NSError *__autoreleasing *)error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
	apr_array_header_t* svnPaths
        = [paths aprArrayWithSize: [paths count]
                  objectConverter: ^(void *destination, id object, SFCAPRPool *pool)
           {
               NSString* pathAsDefiniteString = object;
               if ([object isKindOfClass: [NSURL class]])
               {
                   pathAsDefiniteString = [object absoluteString];
               }
               *(const char**)destination = [pathAsDefiniteString svnStringWithPool: pool]->data;
           }
                             pool: scratchPool];
    svn_error_t* svnError = svn_client_lock(svnPaths,
                                            [comment UTF8String],
                                            stealLock,
                                            [self svnClientContext],
                                    		[scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;

}

-(bool) unlock: (NSArray*) paths
   	 breakLock: (bool) breakLock
         error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
	apr_array_header_t* svnPaths
        = [paths aprArrayWithSize: [paths count]
                  objectConverter: ^(void *destination, id object, SFCAPRPool *pool)
           {
               NSString* pathAsDefiniteString = object;
               if ([object isKindOfClass: [NSURL class]])
               {
                   pathAsDefiniteString = [object absoluteString];
               }
               *(const char**)destination = [pathAsDefiniteString svnStringWithPool: pool]->data;
           }
                             pool: scratchPool];
    
    svn_error_t* svnError = svn_client_unlock(svnPaths,
                                              breakLock,
                                              [self svnClientContext],
                                    		  [scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;
}

-(bool) diffCmdOptions: (NSArray*) diffOptions
                 path1: (NSString*) path1
          	 revision1: (SFCRevision*) revision1
             	 path2: (NSString*) path2
         	 revision2: (SFCRevision*) revision2
     	 relativeToDir: (NSString*) relativeToDir
                 depth: (SFCDepth) depth
               options: (uint32_t) options
		headerEncoding: (NSStringEncoding) headerEncoding
   			 outStream: (NSOutputStream*) outStream
           errorStream: (NSOutputStream*) errorStream
           changeLists: (NSArray*) changeLists
       			 error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
 	apr_array_header_t* svnCmdOptions = [diffOptions aprArrayOfUTF8StringsWithPool: scratchPool];
    apr_array_header_t* svnChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratchPool];
	const char* svnHeaderEncoding = "UTF-8";
    switch (headerEncoding)
    {
        case NSUTF8StringEncoding:
            svnHeaderEncoding = "UTF-8";
        default:
            break;
    }
    svn_error_t* svnError = svn_client_diff6(svnCmdOptions,
                                             [path1 UTF8String],
                                             [revision1 svnOptRevision],
                                             [path2 UTF8String],
                                             [revision2 svnOptRevision],
                                             relativeToDir == nil ? NULL : [relativeToDir UTF8String],
                                             (svn_depth_t)depth,
                                             IS_OPTION_SET(SFC_DIFF_IGNORE_ANCESTRY, options),
                                             IS_OPTION_SET(SFC_DIFF_NO_DIFF_ADDED, options),
                                             IS_OPTION_SET(SFC_DIFF_NO_DIFF_DELETED, options),
                                             IS_OPTION_SET(SFC_DIFF_SHOW_COPIES_AS_ADDS, options),
                                             IS_OPTION_SET(SFC_DIFF_IGNORE_CONTENT_TYPE, options),
                                             IS_OPTION_SET(SFC_DIFF_IGNORE_PROPERTIES, options),
                                             IS_OPTION_SET(SFC_DIFF_PROPERTIES_ONLY, options),
                                             IS_OPTION_SET(SFC_DIFF_USE_GIT_FORMAT, options),
                                             svnHeaderEncoding,
                                             [outStream svnStreamWithPool: scratchPool],
                                             [errorStream svnStreamWithPool: scratchPool],
                                             svnChangeLists,
                                             [self svnClientContext],
                                             [scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;

}

-(bool) addPaths: (NSArray*) paths
    toChangeList: (NSString*) changeList
           depth: (SFCDepth) depth
     changeLists: (NSArray*) changeLists
           error: (NSError *__autoreleasing *)error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
	apr_array_header_t* svnPaths = [paths aprArrayOfUTF8StringsWithPool: scratchPool];
	apr_array_header_t* svnChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratchPool];
	svn_error_t* svnError = svn_client_add_to_changelist(svnPaths,
                                                         [changeList UTF8String],
                                                         (svn_depth_t) depth,
                                                         svnChangeLists,
                                                         [self svnClientContext],
                                                         [scratchPool aprPool]);
    [scratchPool destroy];
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    return ret;
}

-(bool) propGet: (NSString*) propertyName
         target: (NSString*) target
    pegRevision: (SFCRevision*) pegRevision
       revision: (SFCRevision*) revision
          depth: (SFCDepth) depth
    changeLists: (NSArray*) changeLists
     properties: (NSDictionary* __autoreleasing*) properties
      inherited: (NSArray* __autoreleasing*) inheritedProperties
 actualRevision: (SFCRevNum*) actualRevision
          error: (NSError* __autoreleasing *) error
{
    SFCAPRPool* scratchPool = [[self pool] newChild];
    target = [target absolutePath];
    const svn_opt_revision_t* svnPegRevision = NULL;
    const svn_opt_revision_t* svnRevision = NULL;
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }
    if (revision == nil)
    {
        revision = [SFCRevision unspecified];
    }
    svnPegRevision = [pegRevision svnOptRevision];
    svnRevision = [revision svnOptRevision];
    apr_array_header_t* svnChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratchPool];
    apr_hash_t* svnProperties = NULL;
    apr_array_header_t* svnInheritedProperties = NULL;
    svn_revnum_t svnActualRevision;
    svn_error_t* svnError = svn_client_propget5(&svnProperties,
                                                &svnInheritedProperties,
                                                [propertyName UTF8String],
                                                [target UTF8String],
                                                svnPegRevision,
                                                svnRevision,
                                                &svnActualRevision,
                                                (svn_depth_t)depth,
                                                svnChangeLists,
                                                [self svnClientContext],
                                                [scratchPool aprPool],
                                                [scratchPool aprPool]);
    bool ret = svnError == NULL;
	if (ret)
    {
		if (actualRevision != NULL)
        {
            *actualRevision = svnActualRevision;
        }
        if (properties != nil)
        {
            *properties = [NSDictionary dictionaryWithAprHashStringToSVNString: svnProperties];
        }
        if (inheritedProperties != nil)
        {
            *inheritedProperties = [NSArray arrayWithAprArray: svnInheritedProperties
                                              objectConverter:^id(const void *svnObjectPtr, size_t size)
            {
				const svn_prop_inherited_item_t** propItemPtr = (const svn_prop_inherited_item_t**) svnObjectPtr;
                return [[SFCPropertyInheritedItem alloc]
                        initWithSvnPropertyInheritedItem: *propItemPtr];
        	}];
        }
    }
    else
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    [scratchPool destroy];
    return ret;
}

-(bool) propSet: (NSString*) propertyName
          value: (NSString*) value
      wcTargets: (NSArray*) targets
          depth: (SFCDepth) depth
     skipChecks: (bool) skipChecks
    changeLists: (NSArray*) changeLists
          error: (NSError* __autoreleasing*) error
{
	SFCAPRPool* scratch = [[self pool] newChild];

    svn_string_t* svnValue = [value svnStringWithPool: scratch];
    apr_array_header_t* svnTargets = [targets aprArrayOfUTF8StringsWithPool: scratch];
    apr_array_header_t* svnChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratch];
    svn_error_t* svnError = svn_client_propset_local([propertyName UTF8String],
                                                     svnValue,
                                                     svnTargets,
                                                     (svn_depth_t)depth,
                                                     skipChecks,
                                                     svnChangeLists,
                                                     [self svnClientContext],
                                                     [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    [scratch destroy];
    return ret;
}

-(bool) propSet: (NSString*) propertyName
          value: (NSString*) value
     repoTarget: (NSURL*) target
          depth: (SFCDepth) depth
     skipChecks: (bool) skipChecks
       revision: (SFCRevNum) baseRevisionForUrl
revisionProperties: (NSDictionary*) revisionProperties
 committedBlock: (SFCCommittedBlock) commitBlock
          error: (NSError* __autoreleasing*) error
{
	SFCAPRPool* scratch = [[self pool] newChild];
    svn_string_t* svnValue = [value svnStringWithPool: scratch];
    apr_hash_t* svnRevPropTable = [revisionProperties aprHashOfUTF8StringsWithPool: scratch];
	svn_error_t* svnError = svn_client_propset_remote([propertyName UTF8String],
                                                      svnValue,
                                                      [[target absoluteString] UTF8String],
                                                      skipChecks,
                                                      baseRevisionForUrl,
                                                      svnRevPropTable,
                                                      svnCommitCallback,
                                                      (__bridge void *)(commitBlock),
                                                      [self svnClientContext],
                                                      [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) propList: (NSString*) target
     pegRevision: (SFCRevision*) pegRevision
        revision: (SFCRevision*) revision
           depth: (SFCDepth) depth
     changeLists: (NSArray*) changeLists
       inherited: (bool) getInherited
       listBlock: (SFCPropListBlock) receiver
           error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }
    if (revision == nil)
    {
        revision = [SFCRevision unspecified];
    }
	apr_array_header_t* svnChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratch];
    svn_error_t* svnError = svn_client_proplist4([target UTF8String],
                                                 [pegRevision svnOptRevision],
                                                 [revision svnOptRevision],
                                                 (svn_depth_t)depth,
                                                 svnChangeLists,
                                                 getInherited,
                                                 propListReceiver,
                                                 (__bridge void *)(receiver),
                                                 [self svnClientContext],
                                                 [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}


-(bool) cat: (NSString*) pathOrUrl
pegRevision: (SFCRevision*) pegRevision
   revision: (SFCRevision*) revision
     stream: (NSOutputStream*) stream
      error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }
    if (revision == nil)
    {
        revision = [SFCRevision unspecified];
    }
    svn_stream_t* svnStream = [stream svnStreamWithPool: scratch];
    svn_error_t* svnError = svn_client_cat2(svnStream,
                                            [pathOrUrl UTF8String],
                                            [pegRevision svnOptRevision],
                                            [revision svnOptRevision],
                                            [self svnClientContext],
                                            [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) blame: (NSString*) pathOrUrl
  pegRevision: (SFCRevision*) pegRevision
        start: (SFCRevision*) start
          end: (SFCRevision*) end
  diffOptions: (SFCDiffOptions*) diffOptions
ignoreMimeType: (bool) ignoreMimeType
includeMergedRevisions: (bool) includeMergedRevisions
   blameBlock: (SFCBlameBlock) blameBlock
        error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
	if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }
    if (start == nil)
    {
        start = [SFCRevision number: 0];
    }
    if (end == nil)
    {
        end = [SFCRevision head];
    }
    if (diffOptions == nil)
    {
        diffOptions = [SFCDiffOptions defaultOptions];
    }
    svn_error_t* svnError = svn_client_blame5([pathOrUrl UTF8String],
                                              [pegRevision svnOptRevision],
                                              [start svnOptRevision],
                                              [end svnOptRevision],
                                              [diffOptions svnDiffFileOptions],
                                              ignoreMimeType,
                                              includeMergedRevisions,
                                              clientBlameReceiver,
                                              (__bridge void *)(blameBlock),
                                              [self svnClientContext],
                                              [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) list: (NSString*) pathOrUrl
 pegRevision: (SFCRevision*) pegRevision
    revision: (SFCRevision*) revision
       depth: (SFCDepth) depth
  fetchLocks: (bool) fetchLocks
includeExternals: (bool) includeExternals
   listBlock: (SFCListBlock) listBlock
       error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }
    if (revision == nil)
    {
        revision = [SFCRevision unspecified];
    }
    svn_error_t* svnError = svn_client_list3([pathOrUrl UTF8String],
                                             [pegRevision svnOptRevision],
                                             [revision svnOptRevision],
                                             (svn_depth_t)depth,
                                             SVN_DIRENT_ALL,
                                             fetchLocks,
                                             includeExternals,
                                             listFuncReceiver,
                                             (__bridge void *)(listBlock),
                                             [self svnClientContext],
                                             [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) copySources: (NSArray*) sources
        destination: (NSString*) destinationPath
            options: (uint32_t) options
 revisionProperties: (NSDictionary*) revisionProperties
     committedBlock: (SFCCommittedBlock) commitBlock
              error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
	apr_array_header_t* sourceArray = [sources aprArrayWithSize: sizeof(svn_client_copy_source_t*)
                                                objectConverter:^(void *destination, id object, SFCAPRPool *pool)
    {
        *(svn_client_copy_source_t**)destination
        	= [object svnClientCopySource];
    }
                                                           pool: scratch];
    apr_hash_t* svnRevProps = [revisionProperties aprHashOfUTF8StringsWithPool: scratch];
    svn_error_t* svnError = svn_client_copy6(sourceArray,
                         		             [destinationPath UTF8String],
                                             IS_OPTION_SET(SFC_COPY_OPTION_COPY_AS_CHILD, options),
                                   			 IS_OPTION_SET(SFC_COPY_OPTION_MAKE_PARENTS, options),
                                   			 IS_OPTION_SET(SFC_COPY_OPTION_IGNORE_EXTERNALS, options),
                                   			 svnRevProps,
                                   			 commitBlock != nil ? svnCommitCallback : NULL,
                                   			 commitBlock != nil ? (__bridge void *)(commitBlock) : nil,
                                   			 [self svnClientContext],
                                   			 [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) deletePaths: (NSArray*) paths
              force: (bool) force
          keepLocal: (bool) keepLocal
 revisionProperties: (NSDictionary*) revisionProperties
     committedBlock: (SFCCommittedBlock) commitBlock
              error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    apr_array_header_t* svnPaths = [paths aprArrayOfUTF8StringsWithPool: scratch];
    apr_hash_t* svnRevProps = [revisionProperties aprHashOfUTF8StringsWithPool: scratch];
    svn_error_t* svnError = svn_client_delete4(svnPaths,
                                               force,
                                               keepLocal,
                                               svnRevProps,
                                               commitBlock != nil ? svnCommitCallback : NULL,
                                               commitBlock != nil ? (__bridge void *)(commitBlock) : nil,
                                               [self svnClientContext],
                                               [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) import: (NSString*) path
           url: (NSURL*) url
         depth: (SFCDepth) depth
       options: (uint32_t) options
revisionProperties: (NSDictionary*) revisionProperties
   filterBlock: (SFCImportFilter) filterBlock
committedBlock: (SFCCommittedBlock) commitBlock
         error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    apr_hash_t* svnRevProps = [revisionProperties aprHashOfUTF8StringsWithPool: scratch];
	svn_error_t* svnError  = svn_client_import5([path UTF8String],
                                                [[url absoluteString] UTF8String],
                                                (svn_depth_t)depth,
                                                IS_OPTION_SET(SFC_IMPORT_OPTION_NO_IGNORE, options),
                                                IS_OPTION_SET(SFC_IMPORT_OPTION_NO_AUTOPROPS, options),
                                                IS_OPTION_SET(SFC_IMPORT_OPTION_IGNORE_UNKNOWN_NODE_TYPES, options),
                                                svnRevProps,
                                                importFilter,
                                                (__bridge void *)(filterBlock),
                                                svnCommitCallback,
                                                (__bridge void *)(commitBlock),
                                                [self svnClientContext],
                                                [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;

}

-(bool) exportFrom: (NSString*) fromPathOrUrl
                to: (NSString*) path
       pegRevision: (SFCRevision*) pegRevision
          revision: (SFCRevision*) revision
           options: (uint32_t) options
             depth: (SFCDepth) depth
         nativeEol: (NSString*) nativeEol
    resultRevision: (SFCRevNum*) resultRevision
             error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision unspecified];
    }
    if (revision == nil)
    {
        revision = [SFCRevision unspecified];
    }
    svn_revnum_t svnRevNum;
    if ([fromPathOrUrl hasSuffix: @"/"])
    {
        fromPathOrUrl = [fromPathOrUrl substringToIndex:[ fromPathOrUrl length] - 1];
    }

	svn_error_t* svnError = svn_client_export5(&svnRevNum,
                                               [fromPathOrUrl UTF8String],
                                               [path UTF8String],
                                               [pegRevision svnOptRevision],
                                               [revision svnOptRevision],
                                               IS_OPTION_SET(SFC_EXPORT_OPTION_OVERWRITE, options),
                                               IS_OPTION_SET(SFC_EXPORT_OPTION_IGNORE_EXTERNALS, options),
                                               IS_OPTION_SET(SFC_EXPORT_OPTION_IGNORE_KEYWORDS, options),
                                               (svn_depth_t)depth,
                                               [nativeEol UTF8String],
                                               [self svnClientContext],
                                               [scratch aprPool]);

    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
    else
    {
        if (resultRevision != NULL)
        {
            *resultRevision = svnRevNum;
        }
    }
	[scratch destroy];
    return ret;
}

-(bool) mergeSource1: (NSString*) source1
           revision1: (SFCRevision*) revision1
             source2: (NSString*) source2
           revision2: (SFCRevision*) revision2
        targetWcPath: (NSString*) targetWcPath
               depth: (SFCDepth) depth
             options: (uint32_t) options
        mergeOptions: (NSArray*) mergeOptions
               error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    if (revision1 == nil)
    {
        revision1 = [SFCRevision head];
    }
    if (revision2 == nil)
    {
        revision2 = [SFCRevision head];
    }
	apr_array_header_t* svnMergeOptions = [mergeOptions aprArrayOfUTF8StringsWithPool: scratch];
	svn_error_t* svnError = svn_client_merge5([source1 UTF8String],
                                              [revision1 svnOptRevision],
                                              [source2 UTF8String],
                                              [revision2 svnOptRevision],
                                              [targetWcPath UTF8String],
                                              (svn_depth_t)depth,
                                              IS_OPTION_SET(SFC_MERGE_OPTION_IGNORE_MERGE_INFO, options),
                                              IS_OPTION_SET(SFC_MERGE_OPTION_DIFF_IGNORE_ANCESTRY, options),
                                              IS_OPTION_SET(SFC_MERGE_OPTION_FORCE_DELETE, options),
                                              IS_OPTION_SET(SFC_MERGE_OPTION_RECORD_ONLY, options),
                                              IS_OPTION_SET(SFC_MERGE_OPTION_DRY_RUN, options),
                                              IS_OPTION_SET(SFC_MERGE_OPTION_ALLOW_MIXED_REVISIONS, options),
                                              svnMergeOptions,
                                              [self svnClientContext],
                                              [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) moveSources: (NSArray*) sources
        destination: (NSString*) destination
            options: (uint32_t) options
 revisionProperties: (NSDictionary*) revisionProperties
     committedBlock: (SFCCommittedBlock) commitBlock
              error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
    apr_array_header_t* svnSources = [sources aprArrayOfUTF8StringsWithPool: scratch];
	apr_hash_t* svnRevProps = [revisionProperties aprHashOfUTF8StringsWithPool: scratch];
    svn_error_t* svnError = svn_client_move7(svnSources,
                                             [destination UTF8String],
                                             IS_OPTION_SET(SFC_MOVE_OPTION_MOVE_AS_CHILD, options),
                                             IS_OPTION_SET(SFC_MOVE_OPTION_MAKE_PARENTS, options),
                                             IS_OPTION_SET(SFC_MOVE_OPTION_ALLOW_MIXED_REVISIONS, options),
                                             IS_OPTION_SET(SFC_MOVE_OPTION_META_DATA_ONLY, options),
                                             svnRevProps,
                                             commitBlock != nil ? svnCommitCallback : NULL,
                                             (__bridge void *)(commitBlock),
                                             [self svnClientContext],
                                             [scratch aprPool]);

    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) patchFile: (NSString*) patchFile
      workingCopy: (NSString*) workingCopy
          options: (uint32_t) options
       stripCount: (unsigned int) stripCount
       patchBlock: (SFCPatchBlock) patchBlock
            error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];

    svn_error_t* svnError = svn_client_patch([patchFile UTF8String],
                                             [workingCopy UTF8String],
                                             IS_OPTION_SET(SFC_PATCH_OPTION_DRY_RUN, options),
                                             stripCount,
                                             IS_OPTION_SET(SFC_PATCH_OPTION_REVERSE, options),
                                             IS_OPTION_SET(SFC_PATCH_OPTION_IGNORE_WHITE_SPACE, options),
                                             IS_OPTION_SET(SFC_PATCH_OPTION_REMOVE_TEMP_FILES, options),
                                             patchBlock != nil ? patchCallback : NULL,
                                             (__bridge void *)(patchBlock),
                                             [self svnClientContext],
                                             [scratch aprPool]);

    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) relocate: (NSString*) workingCopyPath
            from: (NSURL*) fromURI
              to: (NSURL*) toURI
 ignoreExternals: (bool) ignoreExternals
           error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];
	svn_error_t* svnError = svn_client_relocate2([workingCopyPath UTF8String],
                                                 [[fromURI absoluteString] UTF8String],
                                                 [[toURI absoluteString] UTF8String],
                                                 ignoreExternals,
                                                 [self svnClientContext],
                                                 [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) resolve: (NSString*) path
          depth: (SFCDepth) depth
 conflictChoice: (SFCConflictChoice) conflictChoice
          error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];

    svn_error_t* svnError = svn_client_resolve([path UTF8String],
                                               (svn_depth_t)depth,
                                               (svn_wc_conflict_choice_t)conflictChoice,
                                               [self svnClientContext],
                                               [scratch aprPool]);

    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

-(bool) removePaths: (NSArray*) paths
              depth: (SFCDepth) depth
    fromChangeLists: (NSArray*) changeLists
              error: (NSError* __autoreleasing*) error
{
    SFCAPRPool* scratch = [[self pool] newChild];

    apr_array_header_t* svnPaths = [paths aprArrayOfUTF8StringsWithPool: scratch];
    apr_array_header_t* svnChangeLists = [changeLists aprArrayOfUTF8StringsWithPool: scratch];

    svn_error_t* svnError = svn_client_remove_from_changelists(svnPaths,
                                                               (svn_depth_t)depth,
                                                               svnChangeLists,
                                                               [self svnClientContext],
                                                               [scratch aprPool]);
    bool ret = svnError == NULL;
	if (!ret)
    {
        if (error != NULL)
        {
			*error = [SFCError svnError: svnError];
        }
        svn_error_clear(svnError);
    }
	[scratch destroy];
    return ret;
}

+(NSString*) stringForStatus: (SFCWCStatusKind) numericStatus
{
    NSString* ret = @"??";
    switch (numericStatus)
    {
        case SFC_WC_STATUS_ADDED:
            ret = @"A";
            break;
        case SFC_WC_STATUS_CONFLICTED:
            ret = @"C";
            break;
        case SFC_WC_STATUS_DELETED:
            ret = @"D";
            break;
        case SFC_WC_STATUS_IGNORED:
            ret = @"I";
            break;
        case SFC_WC_STATUS_MODIFIED:
            ret = @"M";
            break;
        case SFC_WC_STATUS_REPLACED:
            ret = @"R";
            break;
        case SFC_WC_STATUS_EXTERNAL:
            ret = @"X";
            break;
        case SFC_WC_STATUS_UNVERSIONED:
            ret = @"?";
            break;
        case SFC_WC_STATUS_MISSING:
            ret = @"!";
            break;
        case SFC_WC_STATUS_OBSTRUCTED:
            ret = @"~";
            break;
        case SFC_WC_STATUS_NONE:	// For unchanged properties
            ret = @" ";
            break;
        /*
         *  Not sure what the offocial letter for the following caaes should be.
         */
        case SFC_WC_STATUS_NORMAL:
            ret = @"n";
            break;
        case SFC_WC_STATUS_MERGED:
            ret = @"m";
            break;
        case SFC_WC_STATUS_INCOMPLETE:
            ret = @"i";
            break;

    }
    return ret;
}


-(void) addAuthenticationProvider: (id<SFCAuthenticationProvider>) newProvider
{
    SFCAuthProviderEntry* newEntry = [[SFCAuthProviderEntry alloc] initWithAuthenticationProvider: newProvider];
    [authenticationProviders addObject: newEntry];
    /*
     *  Initialise the auth baton with an empty set of providers.
     *
     *  TODO: Should probably do something with the error.
     */
    SFCAPRPool* newPool = [SFCAPRPool makePoolWithParent: [self pool]
                                                   error: nil];

    apr_array_header_t* providers = [authenticationProviders aprArrayWithSize: sizeof(svn_auth_provider_object_t*)
                                                              objectConverter:^(void *destination, id object, SFCAPRPool *pool)
    {
		*(svn_auth_provider_object_t**)destination = [object svnAuthProviderObject];
    }
                                                                         pool: newPool];
    svn_auth_open(&_svnClientContext->auth_baton, providers, [newPool aprPool]);
    [self setAuthPool: newPool];
}

+(apr_array_header_t*) aprArrayOfRevisionRangesFrom: (NSArray*) arrayOfRevisionRanges
                                           withPool: (SFCAPRPool*) pool
{
	apr_array_header_t* result = apr_array_make([pool aprPool],
                                                (int)[arrayOfRevisionRanges count],
                                                sizeof (const svn_opt_revision_range_t*));
	[arrayOfRevisionRanges enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
    {
        // TODO: Should really allocate some memory from the pool.
        *(const svn_opt_revision_range_t**)apr_array_push(result) = [obj svnRevisionRange];
    }];
    return result;
}

+(NSDictionary*) dictionaryFromAprAuthParameters: (apr_hash_t *) aprAuthParameters
                                         aprPool: (apr_pool_t*) aprPool
                                           error: (NSError *__autoreleasing *) error
{
    NSMutableDictionary* ret = [[NSMutableDictionary alloc] init];

    for (apr_hash_index_t* index = apr_hash_first(aprPool, aprAuthParameters) ;
         index != NULL ;
         index = apr_hash_next(index))
    {
        const char* key = NULL;
        apr_ssize_t keyLength;
		void* value;

        apr_hash_this(index, (const void**)&key, &keyLength, &value);
		NSLog(@"param = %s", key);
    }

    return ret;
}

@end

svn_error_t* svnCommitCallback(const svn_commit_info_t *commit_info,
                               void *baton,
                               apr_pool_t *pool)
{
	SFCCommittedBlock theBlock = (__bridge SFCCommittedBlock) baton;
    SFCCommitInfo* commitInfo = [[SFCCommitInfo alloc] initWithSvnCommitInfo: commit_info];
	svn_error_t* ret = NULL;

    if (!theBlock(commitInfo))
    {
        ret = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                               NULL,
                               [NSLocalizedString(@"Commit block failed", "") UTF8String]);
    }

    return ret;
}

svn_error_t* svnLogEntryCallback(void *baton,
                                 svn_log_entry_t *log_entry,
                                 apr_pool_t *pool)
{
	SFCLogBlock theBlock = (__bridge SFCLogBlock)(baton);
    SFCLogEntry* logEntry = [[SFCLogEntry alloc] initWithSvnLogEntry: log_entry];
    svn_error_t* svnError = NULL;
	if (!theBlock(logEntry))
    {
        svnError = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                                    NULL,
                                    [NSLocalizedString(@"Log entry block failed", "") UTF8String]);
    }
    return svnError;
}

svn_error_t* svnStatusCallback(void *baton,
                               const char* path,
                               const svn_client_status_t* status,
                               apr_pool_t *pool)
{
    SFCStatusBlock theBlock = (__bridge SFCStatusBlock)(baton);
    SFCStatusEntry* statusEntry = [[SFCStatusEntry alloc] initWithSvnStatusEntry: status];
    svn_error_t* svnError = NULL;
    if (!theBlock([NSString stringWithUTF8String: path], statusEntry))
    {
        svnError = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                                    NULL,
                                    [NSLocalizedString(@"Status callback failed", "") UTF8String]);
    }
    return svnError;
}

void svnNotifyCallback(void* baton,
                       const svn_wc_notify_t* svnNotification,
                       apr_pool_t* pool)
{
    SFCClientNotifyBlock theBlock = (__bridge SFCClientNotifyBlock)(baton);
    SFCWCNotification* notification = [[SFCWCNotification alloc] initWithSvnNotification: svnNotification];
	theBlock(notification);
}

svn_error_t* clientInfoReceiver(void* baton,
                                const char* path,
                                const svn_client_info2_t* svnInfo,
                                apr_pool_t* pool)
{
    bool (^theBlock)(NSString*, SFCClientInfo*, NSError* __autoreleasing*)
    	= (__bridge _Bool (^)(NSString*, SFCClientInfo *__strong, NSError *__autoreleasing *))(baton);
    NSString* myPath = [NSString stringWithUTF8String: path];
    SFCClientInfo* info = [[SFCClientInfo alloc] initWithSvnInfo: svnInfo];
    NSError* error = nil;
    bool blockRet = theBlock(myPath, info, &error);
    svn_error_t* ret = NULL;
    if (!blockRet)
    {
		ret = [SFCError svnErrorFromError: error];
    }
	return ret;
}

apr_status_t cleanUpIterator(void* iterator)
{
    if (iterator != NULL)
    {
        CFRelease(iterator);
    }
    return 0;
}

/*
 *  The authentication provider callbacks merely provide a bridge between the
 *  svn library and Cocoa.  The provider baton will be the 
 *  SFCAuthenticationProvider.
 */
svn_error_t *handleFirstCredentials(void **svnCredentials,
                                    void **iter_baton,
                                    void *provider_baton,
                                    apr_hash_t *aprParameters,
                                    const char *realmstring,
                                    apr_pool_t *pool)
{
    svn_error_t* ret = NULL;
    NSError* error = nil;
	id<SFCAuthenticationProvider> authProvider
    	= (__bridge id<SFCAuthenticationProvider>)(provider_baton);
    if (authProvider != nil)
    {
        NSDictionary* parameters = [SFCClient dictionaryFromAprAuthParameters: aprParameters
                                                                      aprPool: pool
                                                                    	error: &error];
        if (parameters != nil)
        {
            NSString* realm = [NSString stringWithUTF8String: realmstring];
            id theIterator = [authProvider makeIterator];

            SFCCredentials* credentials = [authProvider credentialsForRealm: realm
                                                                 parameters: parameters
                                                                   iterator: theIterator
                                                                  firstTime: true
                                                                      error: &error];
			if (credentials != nil)
            {
                /*
                 *  Hold on to the reference to the iterator by doing a 
                 *  CFRetain and then registering it for being released when the
                 *  APR pool goes away.
                 */
                apr_pool_pre_cleanup_register(pool,
                                              CFBridgingRetain(theIterator),
                                              cleanUpIterator);
                *iter_baton = (__bridge void *)(theIterator);
                /*
                 *  Return the credentials.
                 */
				*svnCredentials = [credentials svnCredentialsWithAprPool: pool];
                error = nil; // Make sure error is nil in success case.
            }
        }
    }
    if (error != nil)
    {
        int errorCode = (int)[error code];

        if (![[error domain] isEqualToString: SVN_ERROR_DOMAIN])
        {
			errorCode += SVN_ERR_LAST + 1;
        }
        ret = svn_error_create((apr_status_t) errorCode,
                               NULL,
                               [[error localizedDescription] UTF8String]);

    }

    return ret;
}

svn_error_t *handleNextCredentials(void **svnCredentials,
                                   void *iter_baton,
                                   void *provider_baton,
                                   apr_hash_t *aprParameters,
                                   const char *realmstring,
                                   apr_pool_t *pool)
{
    NSError* error = nil;
    svn_error_t* ret = NULL;
	id<SFCAuthenticationProvider> authProvider
    	= (__bridge id<SFCAuthenticationProvider>)(provider_baton);
    if (authProvider != nil)
    {
        NSDictionary* parameters = [SFCClient dictionaryFromAprAuthParameters: aprParameters
                                                                      aprPool: pool
                                                                    	error: &error];
        if (parameters != nil)
        {
            NSString* realm = [NSString stringWithUTF8String: realmstring];

            SFCCredentials* credentials = [authProvider credentialsForRealm: realm
                                                                 parameters: parameters
                                                                   iterator: (__bridge id)(iter_baton)
                                                                  firstTime: false
                                                                      error: &error];
			if (credentials != nil)
            {
				*svnCredentials = [credentials svnCredentialsWithAprPool: pool];
                error = nil; // Make sure error is nil lin success case.
            }
        }
    }

    if (error != nil)
    {
        ret = svn_error_create((apr_status_t)[error code] + SVN_ERR_LAST + 1,
                               NULL,
                               [[error localizedDescription] UTF8String]);

    }
    return ret;

}

svn_error_t *handleSaveCredentials(svn_boolean_t *saved,
                                   void *svnCredentials,
                                   void *provider_baton,
                                   apr_hash_t *aprParameters,
                                   const char *realmstring,
                                   apr_pool_t *pool)
{
    svn_error_t* ret = NULL;
    NSError* error = nil;
	id<SFCAuthenticationProvider> authProvider
    	= (__bridge id<SFCAuthenticationProvider>)(provider_baton);
    NSDictionary* parameters = [SFCClient dictionaryFromAprAuthParameters: aprParameters
                                                                  aprPool: pool
                                                                    error: &error];
    if (parameters != nil)
    {
        NSString* realm = [NSString stringWithUTF8String: realmstring];
        SFCCredentials* credentials = [SFCCredentials credentialsForKind: [authProvider kind]
                                                          svnCredentials: svnCredentials];
        *saved = [authProvider saveCredentials: credentials
                                      forRealm: realm
                                    parameters: parameters error: &error];
        *saved = true;
        if (*saved)
        {
			error = nil; // Make sure in success case, error is nil.
        }
    }
    if (error != nil)
    {
        ret = svn_error_create((apr_status_t)[error code] + SVN_ERR_LAST + 1,
                               NULL,
                               [[error localizedDescription] UTF8String]);

    }
    return ret;
}

static svn_error_t* handleLogMessage(const char** svnMessage,
                                     const char** svnTempFile,
                                     const apr_array_header_t* svnCommitItems,
                                     void* baton,
                                     apr_pool_t* pool)
{
	NSString* message = nil;
    NSString* tempFile = nil;
    SFCGetCommitLog logBlock = (__bridge SFCGetCommitLog)(baton);
    NSArray* commitItems = [NSArray arrayWithAprArray: svnCommitItems
                                      objectConverter:^id(const void *svnObject, size_t size)
                                      {
                                          return [[SFCCommitItem alloc] initWithSvnCommitItem: svnObject];
                                      }];
    svn_error_t* ret = NULL;
	if (logBlock(commitItems, &message, &tempFile))
    {
        *svnMessage = NULL;
        *svnTempFile = NULL;
        if (message != nil)
        {
            svn_string_t* svnMessageRet = [message svnStringWithAprPool: pool];
            *svnMessage = svnMessageRet->data;
            if (tempFile != nil)
            {
                svn_string_t* svnTempFileRet = [tempFile svnStringWithAprPool: pool];
                *svnTempFile = svnTempFileRet->data;
            }
        }
    }
    else
    {
        ret = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                               NULL,
                               [NSLocalizedString(@"Failed to get commit log message", "") UTF8String]);
    }
    return ret;
}

static svn_error_t* clientBlameReceiver(void *baton,
                                        svn_revnum_t start_revnum,
                                        svn_revnum_t end_revnum,
                                        apr_int64_t line_no,
                                        svn_revnum_t revision,
                                        apr_hash_t *rev_props,
                                        svn_revnum_t merged_revision,
                                        apr_hash_t *merged_rev_props,
                                        const char *merged_path,
                                        const char *line,
                                        svn_boolean_t local_change,
                                        apr_pool_t *pool)
{
    SFCBlameBlock blameBlock = (__bridge SFCBlameBlock)(baton);
    NSDictionary* revisionProperties = [NSDictionary dictionaryWithAprHashStringToSVNString: rev_props];
    NSDictionary* mergedRevisionProperties
    	= merged_rev_props == NULL ? nil : [NSDictionary dictionaryWithAprHashStringToSVNString: merged_rev_props];

    NSString* mergedPath
    	= merged_path == NULL ? nil : [NSString stringWithUTF8String: merged_path];

    svn_error_t* svnError = NULL;
	if (!blameBlock(start_revnum,
                    end_revnum,
                    line_no,
                    revision,
                    revisionProperties,
                    merged_revision,
                    mergedRevisionProperties,
                    mergedPath,
                    [NSString stringWithUTF8String: line],
                    local_change))
    {
		svnError = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                                    NULL,
                                    [NSLocalizedString(@"Blame block failed", "") UTF8String]);
    }
    return svnError;
}

svn_error_t* listFuncReceiver(void* baton,
                              const char* svnPath,
                              const svn_dirent_t* dirEnt,
                              const svn_lock_t* svnLock,
                              const char* absPath,
                              const char* svnExternParentUrl,
                              const char* svnExternTarget,
                              apr_pool_t* pool)
{
    SFCListBlock listBlock = (__bridge SFCListBlock)(baton);
    SFCDirectoryEntry* directoryEntry = [[SFCDirectoryEntry alloc] initWithSvnDirEnt: dirEnt];
	SFCLock* lock = svnLock != NULL ? [[SFCLock alloc] initWithSvnLock: svnLock]
                                    : nil;
    NSURL* externParentUrl = svnExternParentUrl != NULL ? [NSURL URLWithString: [NSString stringWithUTF8String: svnExternParentUrl]]
    													: nil;
    NSString* externTarget = svnExternTarget != NULL ? [NSString stringWithUTF8String: svnExternTarget]
    												 : nil;


    svn_error_t* ret = NULL;
    if (! listBlock([NSString stringWithUTF8String: svnPath],
                    directoryEntry,
                    lock,
                    [NSString stringWithUTF8String: absPath],
                    externParentUrl,
                    externTarget))
    {
		ret = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                               NULL,
                               [NSLocalizedString(@"List block failed", "") UTF8String]);
    }
    return ret;
}

svn_error_t* propListReceiver(void *baton,
                              const char *svnPath,
                              apr_hash_t *svnPropHash,
                              apr_array_header_t *svnInheritedProps,
                              apr_pool_t *pool)
{
	SFCPropListBlock listBlock = (__bridge SFCPropListBlock)(baton);
    NSString* path = svnPath != nil ? [NSString stringWithUTF8String: svnPath]
    								: nil;
    NSArray* inheritedProperties = nil;
    if (svnInheritedProps != nil)
    {
        inheritedProperties = [NSArray arrayWithAprArray: svnInheritedProps
                                          objectConverter:^id(const void *svnObjectPtr, size_t size)
                                {
                                    const svn_prop_inherited_item_t** propItemPtr = (const svn_prop_inherited_item_t**) svnObjectPtr;
                                    return [[SFCPropertyInheritedItem alloc]
                                            initWithSvnPropertyInheritedItem: *propItemPtr];
                                }];
    }
    NSDictionary* properties = nil;
	if (svnPropHash != nil)
    {
        properties = [NSDictionary dictionaryWithAprHashStringToSVNString: svnPropHash];
    }
    svn_error_t* ret = NULL;
    if (!listBlock(path, properties, inheritedProperties))
    {
		ret = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                               NULL,
                               [NSLocalizedString(@"List block failed", "") UTF8String]);
    }
    return ret;
}

svn_error_t*
importFilter(void *baton,
             svn_boolean_t *filtered,
             const char *local_abspath,
             const svn_io_dirent2_t *dirent,
             apr_pool_t *scratch_pool)
{
    SFCImportFilter theBlock = (__bridge SFCImportFilter)(baton);
    bool errorOccurred = false;
    SFCIODirectoryEntry* directoryEntry = [[SFCIODirectoryEntry alloc] initWithSvnIODirEnt: dirent];
    *filtered = theBlock([NSString stringWithUTF8String: local_abspath],
                         directoryEntry,
                         &errorOccurred);
    svn_error_t* result = NULL;
    if (errorOccurred)
    {
        result = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                                  NULL,
                                  [NSLocalizedString(@"Filter block failed", "") UTF8String]);
    }
    return result;
}

svn_error_t* patchCallback(void *baton,
                          svn_boolean_t *filtered,
                          const char *canon_path_from_patchfile,
                          const char *patch_abspath,
                          const char *reject_abspath,
                          apr_pool_t *scratch_pool)
{
    bool errorOccurred = false;
    SFCPatchBlock theBlock = (__bridge SFCPatchBlock)(baton);
    *filtered = !theBlock([NSString stringWithUTF8String: canon_path_from_patchfile],
                          [NSString stringWithUTF8String: patch_abspath],
                          [NSString stringWithUTF8String: reject_abspath],
                          &errorOccurred);

    svn_error_t* result = NULL;
    if (errorOccurred)
    {
        result = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                                  NULL,
                                  [NSLocalizedString(@"Filter block failed", "") UTF8String]);
    }
    return result;
}

svn_error_t* conflictResolver(svn_wc_conflict_result_t **result,
                              const svn_wc_conflict_description2_t *description,
                              void *baton,
                              apr_pool_t *resultPool,
                              apr_pool_t *scratchPool)
{
	SFCConfictResolverBlock theBlock = (__bridge SFCConfictResolverBlock)(baton);

    SFCConflictDescription* conflictDescription = [[SFCConflictDescription alloc] initWithSvnDescription: description];
    SFCConflictResult* myResult = theBlock(conflictDescription);
    svn_error_t* svnError = NULL;
    if (myResult == nil)
    {
        svnError = svn_error_create(SFC_CALLBACK_ERROR + SVN_ERR_LAST,
                                  	NULL,
                                  	[NSLocalizedString(@"Conflict block failed", "") UTF8String]);
    }
    else
    {
        *result = [myResult svnConflictResultWithAprPool: resultPool];
    }
    return svnError;
}

@implementation SFCCommitInfo

@synthesize revision = _revision;
@synthesize date = _date;
@synthesize author = _author;
@synthesize postCommitError = _postCommitError;
@synthesize repositoryRoot = _repositoryRoot;

-(id) initWithSvnCommitInfo: (const svn_commit_info_t*) svnCommitInfo
{
    self = [super init];
    if (self != nil)
    {
		_revision = svnCommitInfo->revision;
        _date = [NSString stringWithUTF8String: svnCommitInfo->date];
        _author = [NSString stringWithUTF8String: svnCommitInfo->author];
        if (svnCommitInfo->post_commit_err != NULL)
        {
            _postCommitError = [NSString stringWithUTF8String: svnCommitInfo->post_commit_err];
        }
        _repositoryRoot = [NSString stringWithUTF8String: svnCommitInfo->repos_root];
    }
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"Commit: rev %ld at %@ by %@, post commit error %@, root %@",
            (long)[self revision],
            [self date],
            [self author],
            [self postCommitError],
            [self repositoryRoot]];
}

@end

@implementation SFCWorkingCopyInfo

-(id) initWithSvnWorkingCopyInfo: (const svn_wc_info_t*) svnWorkingCopyInfo
{
	self = [super init];
	if (self != nil)
    {
		// TODO: Fill in the properties
    }
    return self;
}

@end

@implementation SFCClientInfo

@synthesize URL = _URL;
@synthesize revision = _revision;
@synthesize repositoryRoot = _repositoryRoot;
@synthesize UUID = _UUID;
@synthesize kind = _kind;
@synthesize fileSize = _fileSize;
@synthesize lastChangedRevision = _lastChangedRevision;
@synthesize lastChangedDate = _lastChangedDate;
@synthesize lastChangedAuthor = _lastChangedAuthor;
@synthesize lock = _lock;
@synthesize workingCopyInfo = _workingCopyInfo;

-(id) initWithSvnInfo: (const svn_client_info2_t*) svnInfo
{
    self = [super init];
    if (self != nil)
    {
		_URL = [NSURL URLWithString: [NSString stringWithUTF8String: svnInfo->URL]];
        _revision = svnInfo->rev;
        _repositoryRoot = [NSURL URLWithString: [NSString stringWithUTF8String:
                                                 svnInfo->repos_root_URL]];
        _UUID = [NSString stringWithUTF8String: svnInfo->repos_UUID];
		_kind = (SFCNodeKind)svnInfo->kind;
        _fileSize = svnInfo->size;
        _lastChangedRevision = svnInfo->last_changed_rev;
		_lastChangedDate = [NSDate dateWithAprTime: svnInfo->last_changed_date];
        _lastChangedAuthor = [NSString stringWithUTF8String: svnInfo->last_changed_author];
        _lock = [[SFCLock alloc] initWithSvnLock: svnInfo->lock];
        if (svnInfo->wc_info != NULL)
        {
            _workingCopyInfo = [[SFCWorkingCopyInfo alloc] initWithSvnWorkingCopyInfo: svnInfo->wc_info];
        }
    }
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"Info: url %@, rev %ld, root %@",
            [self URL],
            (long) [self revision],
            [self repositoryRoot]];
}


@end

@implementation SFCLogEntry

@synthesize revision = _revision;
@synthesize hasChildren = _hasChildren;
@synthesize nonInheritable = _nonInheritable;
@synthesize subtractiveMerge = _subtractiveMerge;
@synthesize revisionProperties = _revisionProperties;
@synthesize changedPaths = _changedPaths;

-(id) initWithSvnLogEntry: (const svn_log_entry_t*) svnLogEntry
{
    self = [super init];
    if (self != nil)
    {
		_revision = svnLogEntry->revision;
        _hasChildren = svnLogEntry->has_children;
        _nonInheritable = svnLogEntry->non_inheritable;
        _subtractiveMerge = svnLogEntry->subtractive_merge;
        _revisionProperties = [NSDictionary dictionaryWithAprHashStringToSVNString: svnLogEntry->revprops];
		_changedPaths = [NSDictionary
                         dictionaryWithAprHash: svnLogEntry->changed_paths2
                                  keyConverter:^id(const void *svnObject, size_t size)
                                    {
                                        return [NSString stringWithUTF8String:(const char *) svnObject];
                                    }
                                valueConverter:^id(const void *svnObject, size_t size)
        							{
                                        return [[SFCLogChangedPath alloc]
                                                initWithSvnLogChangedPath: (const svn_log_changed_path2_t*) svnObject];
                                    }];
   	}
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"log entry: rev %ld revision properties %@, changed paths %@",
            [self revision],
            [self revisionProperties],
            [self changedPaths]];
}

@end

@implementation SFCLogChangedPath

@synthesize action = _action;
@synthesize copiedFromPath = _copiedFromPath;
@synthesize copiedFromRevision = _copiedFromRevision;
@synthesize kind = _kind;

-(id) initWithSvnLogChangedPath: (const svn_log_changed_path2_t*) svnChangedPath
{
    self = [super init];
    if (self != nil)
    {
        _action = svnChangedPath->action;
        if (svnChangedPath->copyfrom_path != NULL)
        {
            _copiedFromPath = [NSString stringWithUTF8String: svnChangedPath->copyfrom_path];
            _copiedFromRevision = svnChangedPath->copyfrom_rev;
        }
   	}
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"changed path: %c copied from %@:%ld",
            [self action],
            [self copiedFromPath],
            (long)[self copiedFromRevision]];
}

@end

@implementation SFCStatusEntry

@synthesize kind = _kind;
@synthesize localAbsolutePath = _localAbsolutePath;
@synthesize fileSize = _fileSize;
@synthesize isVersioned = _isVersioned;
@synthesize isConflicted = _isConflicted;
@synthesize nodeStatus = _nodeStatus;
@synthesize propertyStatus = _propertyStatus;
@synthesize isLocked = _isLocked;
@synthesize isCopied = _isCopied;
@synthesize repositoryRootURL = _repositoryRootURL;
@synthesize repositoryUUID = _repositoryUUID;
@synthesize repositoryRelativePath = _repositoryRelativePath;
@synthesize revision;
@synthesize changedRevision;
@synthesize changedDate;
@synthesize changedAuthor;
@synthesize isSwitched;
@synthesize isExternal;
@synthesize lock;
@synthesize changeList;
@synthesize depth;
@synthesize outOfDateKind;
@synthesize repositoryPropertyStatus;
@synthesize repositoryLock;
@synthesize outOfDateChangedRevision;
@synthesize outOfDateChangedDate;
@synthesize outOfDateChangedAuthor;
@synthesize movedFromAbsPath;
@synthesize movedToAbsPath;



-(id) initWithSvnStatusEntry: (const svn_client_status_t*) svnStatus
{
    self = [super init];
    if (self != nil)
    {
        _kind = (SFCNodeKind)svnStatus->kind;
        _localAbsolutePath = [NSString stringWithUTF8String: svnStatus->local_abspath];
        _nodeStatus = (SFCWCStatusKind)svnStatus->node_status;
        _propertyStatus = (SFCWCStatusKind)svnStatus->prop_status;
   	}
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"kind %ld local path %@",
            (long)[self kind],
            [self localAbsolutePath]];
}

@end

@implementation SFCWCNotification

-(id) initWithSvnNotification: (const svn_wc_notify_t*) svnNotification
{
	self = [super init];
    if (self != nil)
    {

    }
    return self;
}

@end

@implementation SFCPropertyInheritedItem

@synthesize pathOrURL = _pathOrURL;
@synthesize properties = _properties;

-(id) initWithSvnPropertyInheritedItem: (const svn_prop_inherited_item_t*) svnPit
{
	self = [super init];
    if (self != nil)
    {
		_pathOrURL = [NSString stringWithUTF8String: svnPit->path_or_url];
        _properties = [NSDictionary dictionaryWithAprHashStringToSVNString: svnPit->prop_hash];
    }
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"<%@ %@, %@>", [self className], [self pathOrURL], [self properties]];
}

@end

@interface SFCDiffOptions ()

-(id) initWithIgnoreSpace: (SFCDiffFileIgnoreSpace) ignoreSpace
           ignoreEOLStyle: (bool) eolStyle
           showCFunctions: (bool) showCFunctions;

@end

@implementation SFCDiffOptions
{
    svn_diff_file_options_t _svnOptions;
}

-(id) initWithIgnoreSpace: (SFCDiffFileIgnoreSpace) ignoreSpace
           ignoreEOLStyle: (bool) eolStyle
           showCFunctions: (bool) showCFunctions
{
    self = [super init];
    if (self != nil)
    {
        _svnOptions.ignore_space = (svn_diff_file_ignore_space_t)ignoreSpace;
        _svnOptions.ignore_eol_style = eolStyle;
        _svnOptions.show_c_function = showCFunctions;
    }
    return self;
}

-(id) init
{
	return [self initWithIgnoreSpace: SFC_DIFF_IGNORE_SPACE_NONE
                      ignoreEOLStyle: false showCFunctions: false];
}

+(SFCDiffOptions*) defaultOptions
{
    static SFCDiffOptions* theOptions = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        theOptions = [SFCDiffOptions ignoreSpace: SFC_DIFF_IGNORE_SPACE_NONE
                                  ignoreEOLStyle: false
                                  showCFunctions: false];
    });
    return theOptions;
}

+(SFCDiffOptions*) ignoreSpace: (SFCDiffFileIgnoreSpace) ignoreSpace
                ignoreEOLStyle: (bool) eolStyle
                showCFunctions: (bool) showCFunctions
{
    return [[SFCDiffOptions alloc] initWithIgnoreSpace: ignoreSpace
                                        ignoreEOLStyle: eolStyle
                                        showCFunctions: showCFunctions];
}

-(SFCDiffFileIgnoreSpace) ignoreSpace
{
    return (SFCDiffFileIgnoreSpace)_svnOptions.ignore_space;
}

-(bool) ignoreEOLStyle
{
    return _svnOptions.ignore_eol_style;
}

-(bool) showCFunction
{
    return _svnOptions.show_c_function;
}

-(svn_diff_file_options_t*) svnDiffFileOptions
{
    return &_svnOptions;
}

@end

@implementation SFCDirectoryEntry

@synthesize kind = _kind;
@synthesize fileSize = _fileSize;
@synthesize hasProperties = _hasProperties;
@synthesize createdRevision = _createdRevision;
@synthesize createdDate = _createdDate;
@synthesize lastAuthor = _lastAuthor;

-(id) initWithSvnDirEnt: (const svn_dirent_t*) svnDirEnt
{
    self = [super init];
    if (self != nil)
    {
		_kind = (SFCNodeKind)svnDirEnt->kind;
        _fileSize = svnDirEnt->size;
        _createdRevision = svnDirEnt->created_rev;
        _createdDate = [NSDate dateWithAprTime: svnDirEnt->time];
        _lastAuthor = [NSString stringWithUTF8String: svnDirEnt->last_author];
    }
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"<%@ %d, %lu, %ld, %@ %@>",
            [self className],
            (int)[self kind],
            [self fileSize],
            [self createdRevision],
            [self createdDate],
            [self lastAuthor]];
}

@end

@implementation SFCVersion

@synthesize svnMajorVersion = _svnMajorVersion;
@synthesize svnMinorVersion = _svnMinorVersion;
@synthesize svnPatchVersion = _svnPatchVersion;
@synthesize svnTag = _svnTag;

-(id) init
{
    self = [super init];
    if (self != nil)
    {
        const svn_version_t* svnVersion = svn_client_version();
        _svnMajorVersion = svnVersion->major;
        _svnMinorVersion = svnVersion->minor;
        _svnPatchVersion = svnVersion->patch;
        _svnTag = [NSString stringWithUTF8String: svnVersion->tag];

    }
    return self;
}

-(int) frameworkMajorVersion
{
    return 1;
}

-(NSString*) description
{
    return [NSString stringWithFormat:
            @"Framewotk version %d, svn client version %d.%d.%d, tag %@",
            [self frameworkMajorVersion],
            [self svnMajorVersion],
            [self svnMinorVersion],
            [self svnPatchVersion],
            [self svnTag]];
}

@end

@interface SFCClientCopySource ()

-(id) initWithPath: (NSString*) path
       pegRevision: (SFCRevision*) pegRevision
          revision: (SFCRevision*) revision;

@end

@implementation SFCClientCopySource
{
    svn_client_copy_source_t _svnClientCopySource;
}
@synthesize path = _path;
@synthesize pegRevision = _pegRevision;
@synthesize revision = _revision;


-(id) init
{
	return [self initWithPath: nil pegRevision: nil revision: nil];
}

-(id) initWithPath: (NSString*) path
       pegRevision: (SFCRevision*) pegRevision
          revision: (SFCRevision*) revision
{
	self = [super init];
    if (self != nil)
    {
        _path = path;
        _pegRevision = pegRevision;
        _revision = revision;
        _svnClientCopySource.path = [path UTF8String];
        _svnClientCopySource.peg_revision = [pegRevision svnOptRevision];
        _svnClientCopySource.revision = [revision svnOptRevision];
    }
    return self;
}

+(SFCClientCopySource*) sourceFromPath: (NSString*) path
                           pegRevision: (SFCRevision*) pegRevision
							  revision: (SFCRevision*) revision
{
    if (pegRevision == nil)
    {
        pegRevision = [SFCRevision head];
    }
    if (revision == nil)
    {
        revision = [SFCRevision unspecified];
    }
    return [[self alloc] initWithPath: path pegRevision: pegRevision revision: revision];
}

+(SFCClientCopySource*) sourceFromWorkingCopyPath: (NSString*) path
{
    return [self sourceFromPath: path
                    pegRevision: [SFCRevision head]
                       revision: [SFCRevision working]];
}

-(svn_client_copy_source_t*) svnClientCopySource
{
	return &_svnClientCopySource;
}

@end

@implementation SFCIODirectoryEntry

@synthesize kind =_kind;
@synthesize fileSize = _fileSize;
@synthesize isSpecial = _isSpecial;
@synthesize modificationTime = _modificationTime;


-(id) initWithSvnIODirEnt:(const svn_io_dirent2_t*) dirEnt
{
    self = [super init];
    if (self != nil)
    {
		_fileSize = dirEnt->filesize;
        _isSpecial = dirEnt->special;
        _kind = (SFCNodeKind) dirEnt->kind;
        _modificationTime = [NSDate dateWithAprTime: dirEnt->mtime];
    }
    return self;
}

@end

#define SAFE_STRING(V)	((V) == NULL ? nil : [NSString stringWithUTF8String: (V)])

@implementation SFCConflictDescription

@synthesize nodeKind = _nodeKind;
@synthesize propertyName = _propertyName;
@synthesize isBinary = _isBinary;
@synthesize mimeType = _mimeType;
@synthesize confictAction = _confictAction;
@synthesize conflictReason = _conflictReason;
@synthesize baseAbsPath = _baseAbsPath;
@synthesize theirAbsPath = _theirAbsPath;
@synthesize myAbsPath = _myAbsPath;
@synthesize mergedFile = _mergedFile;
@synthesize operation = _operation;
@synthesize sourceLeftVersion = _sourceLeftVersion;
@synthesize sourceRightVersion = _sourceRightVersion;
@synthesize localAbsPath = _localAbsPath;
@synthesize conflictKind = _conflictKind;




-(id) initWithSvnDescription:(const svn_wc_conflict_description2_t *)svnDescription
{
	self = [super init];
    if (self != nil)
    {
		_localAbsPath = SAFE_STRING(svnDescription->local_abspath);
        _nodeKind = (SFCNodeKind)svnDescription->node_kind;
        _conflictKind = (SFCConflictKind)svnDescription->kind;
        _propertyName = SAFE_STRING(svnDescription->property_name);
        _isBinary = svnDescription->is_binary;
        _mimeType = SAFE_STRING(svnDescription->mime_type);
        _confictAction	= (SFCConflictAction) svnDescription->action;
        _conflictReason = (SFCConflictReason) svnDescription->reason;
		_baseAbsPath = SAFE_STRING(svnDescription->base_abspath);
		_theirAbsPath = SAFE_STRING(svnDescription->their_abspath);
		_myAbsPath = SAFE_STRING(svnDescription->my_abspath);
		_mergedFile = SAFE_STRING(svnDescription->merged_file);
        _operation = (SFCOperation) svnDescription->operation;
        _sourceLeftVersion = [[SFCConflictVersion alloc] initWithSvnVersion: svnDescription->src_left_version];
        _sourceRightVersion = [[SFCConflictVersion alloc] initWithSvnVersion: svnDescription->src_right_version];
    }
    return self;
}

@end

@implementation SFCConflictResult

@synthesize choice = _choice;
@synthesize  mergedFile = _mergedFile;
@synthesize  saveMerged = _saveMerged;


-(id) init
{
    return [self initWithChoice: SFC_CONFLICT_CHOICE_UNSPECIFIED
                     mergedFile: nil
                     saveMerged: false];
}

-(id) initWithChoice: (SFCConflictChoice) choice
          mergedFile: (NSString*) mergedFile
		  saveMerged:(bool)saveMerged
{
	self = [super init];
    if (self != nil)
    {
        _choice = choice;
        _mergedFile = [mergedFile copy];
        _saveMerged = saveMerged;
    }
    return self;
}

-(svn_wc_conflict_result_t*) svnConflictResultWithAprPool: (apr_pool_t*) pool
{
    /*
     *  We will be using svn_wc_create_conflict_result which shallow copies
     *  the fmerge file name.  So we will create our own deep copy from the
     *  same pool so that it lasts as long as the conflict result.
     */
    const char* mergedFileString = [[self mergedFile] UTF8String];
    const char* allocatedMergedString = NULL;
    if (mergedFileString != NULL)
    {
        svn_string_t* mergedFileString = [[self mergedFile] svnStringWithAprPool: pool];
        allocatedMergedString = mergedFileString->data;
    }
    svn_wc_conflict_result_t* ret = svn_wc_create_conflict_result((svn_wc_conflict_choice_t)[self choice],
                                                                  allocatedMergedString,
                                                                  pool);

    return ret;
}

@end

@implementation SFCConflictVersion

@synthesize nodeKind = _nodeKind;
@synthesize uuid = _uuid;
@synthesize repositoryURI = _repositoryURI;
@synthesize pegRevision = _pegRevision;
@synthesize pathInRepository = _pathInRepository;


-(id) init
{
    return [self initWithSvnVersion: NULL];
}

-(id) initWithSvnVersion: (const svn_wc_conflict_version_t*) svnVersion
{
	self = [super init];
    if (self != nil)
    {
		if (svnVersion != NULL)
        {
			_nodeKind = (SFCNodeKind)svnVersion->node_kind;
            _uuid = [NSString stringWithUTF8String: svnVersion->repos_uuid];
            _repositoryURI = [NSURL URLWithString:
                              [NSString stringWithUTF8String: svnVersion->repos_url]];
            _pegRevision = svnVersion->peg_rev;
            _pathInRepository = [NSString stringWithUTF8String: svnVersion->path_in_repos];
        }
        else
        {
            _nodeKind = SFC_NODE_KIND_UNKNOWN;
        }
    }
    return self;
}

@end